{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "256e246f-d908-4b29-b8f9-adaab145ee56",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://repo.huaweicloud.com/repository/pypi/simple/\n",
      "Requirement already satisfied: download in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (0.3.5)\n",
      "Requirement already satisfied: tqdm in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from download) (4.67.1)\n",
      "Requirement already satisfied: six in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from download) (1.16.0)\n",
      "Requirement already satisfied: requests in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from download) (2.32.3)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from requests->download) (3.4.1)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from requests->download) (3.10)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from requests->download) (2.3.0)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from requests->download) (2025.1.31)\n",
      "\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.2\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpython -m pip install --upgrade pip\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "!pip install download"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "10f8e074-f48d-4806-a618-4c4ddbad060b",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading data from https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz (162.2 MB)\n",
      "\n",
      "file_sizes: 100%|█████████████████████████████| 170M/170M [00:00<00:00, 257MB/s]\n",
      "Extracting tar.gz file...\n",
      "Successfully downloaded / unzipped to ./datasets-cifar10-bin\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'./datasets-cifar10-bin'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from download import download\n",
    "\n",
    "url = \"https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz\"\n",
    "\n",
    "download(url, \"./datasets-cifar10-bin\", kind=\"tar.gz\", replace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "ace29469-1239-4be4-b88b-8197bdfd8cec",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import mindspore as ms\n",
    "import mindspore.dataset as ds\n",
    "import mindspore.dataset.vision as vision # 图像预处理工具\n",
    "import mindspore.dataset.transforms as transforms  # 数据转换工具\n",
    "from mindspore import dtype as mstype\n",
    "\n",
    "data_dir = \"./datasets-cifar10-bin/cifar-10-batches-bin\"  # 数据集根目录\n",
    "batch_size = 256  # 批量大小\n",
    "image_size = 32  # 训练图像空间大小\n",
    "workers = 4  # 并行线程个数\n",
    "num_classes = 10  # 分类数量\n",
    "\n",
    "\n",
    "def create_dataset_cifar10(dataset_dir, usage, resize, batch_size, workers):\n",
    "\n",
    "    data_set = ds.Cifar10Dataset(dataset_dir=dataset_dir, # 数据集路径\n",
    "                                 usage=usage, # 用途：\"train\"（训练集）或 \"test\"（测试集）\n",
    "                                 num_parallel_workers=workers,  # 并行加载线程数\n",
    "                                 shuffle=True) # 加载时是否打乱数据（训练集通常需要打乱）\n",
    "\n",
    "    trans = [] # 存储图像变换操作的列表\n",
    "    if usage == \"train\":\n",
    "        trans += [\n",
    "            vision.RandomCrop((32, 32), (4, 4, 4, 4)),  # 随机裁剪：先将图像填充4像素，再随机裁剪为32x32\n",
    "            vision.RandomHorizontalFlip(prob=0.5) # 随机水平翻转（50%概率）\n",
    "        ]\n",
    "# 训练集和测试集共有的基础预处理\n",
    "    trans += [\n",
    "        vision.Resize(resize), # 将图像大小调整为指定尺寸（这里是32x32）\n",
    "        vision.Rescale(1.0 / 255.0, 0.0),  # 像素值归一化：将 [0,255] 映射到 [0,1]\n",
    "        vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),  # 标准化：按通道减去均值、除以标准差\n",
    "        vision.HWC2CHW()\n",
    "        # 通道顺序转换：从 (高度, 宽度, 通道) 转为 (通道, 高度, 宽度)（适配深度学习框架习惯）\n",
    "] \n",
    "   \n",
    "\n",
    "    target_trans = transforms.TypeCast(mstype.int32)  # 将标签转换为 int32 类型\n",
    "\n",
    "    # 数据映射操作\n",
    "    data_set = data_set.map(operations=trans,\n",
    "                            input_columns='image', # 针对数据集中的 \"image\" 列进行处理\n",
    "                            num_parallel_workers=workers)\n",
    "\n",
    "    data_set = data_set.map(operations=target_trans,\n",
    "                            input_columns='label', # 针对数据集中的 \"label\" 列进行处理\n",
    "                            num_parallel_workers=workers)\n",
    "\n",
    "    # 批量操作\n",
    "    data_set = data_set.batch(batch_size)\n",
    "\n",
    "    return data_set\n",
    "\n",
    "\n",
    "# 获取处理后的训练与测试数据集\n",
    "\n",
    "dataset_train = create_dataset_cifar10(dataset_dir=data_dir,\n",
    "                                       usage=\"train\",\n",
    "                                       resize=image_size,\n",
    "                                       batch_size=batch_size,\n",
    "                                       workers=workers)\n",
    "step_size_train = dataset_train.get_dataset_size()\n",
    "\n",
    "dataset_val = create_dataset_cifar10(dataset_dir=data_dir,\n",
    "                                     usage=\"test\",\n",
    "                                     resize=image_size,\n",
    "                                     batch_size=batch_size,\n",
    "                                     workers=workers)\n",
    "step_size_val = dataset_val.get_dataset_size()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "2848e2a8-2427-4551-83ba-0f713dca4020",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Image shape: (256, 3, 32, 32), Label shape: (256,)\n",
      "Labels: [0 3 2 5 0 5]\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgMAAAGFCAYAAABg2vAPAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8ekN5oAAAACXBIWXMAAA9hAAAPYQGoP6dpAABmB0lEQVR4nO29eZQd1ZXmuyPujTvlzXnSrNQsMQoLY5tJGBcIbMyDboPpen4I2gNDlV2uNjXYvYzxsIq1qopepnG3sfu5BLioKlp4HgrbuMCNbQwYJMAIkIQmlJIylcPN4eadYnh/0J3PexB5naBMkfH91vJaPkc7TpyIOBF5MvfHt50oiiICAAAAQGxxZ3sCAAAAAJhdsBkAAAAAYg42AwAAAEDMwWYAAAAAiDnYDAAAAAAxB5sBAAAAIOZgMwAAAADEHGwGAAAAgJiDzQAAAAAQc2K3GXj00UfJcRx69NFH3xLjAgDAXOe2224jx3FoYGDgdeN6enrouuuue0PnuuCCC+iCCy54Q2PMRWK3GQAA/GEcOnSIbrvtNtq+fftsTwUAcJxIzvYEZprzzz+fSqUSpVKp2Z4KAG8JDh06RJ///Oepp6eH1q9fP9vTATHm5ZdfJtfF77DHg9jdVdd1KZPJTLmgJiYmZmhGAAAA6iGdTpPnea8bUywWZ2g2c4s5sxnYv38/3XzzzbRmzRrKZrPU3t5OV111Fe3bt4/FWbn9Cy64gE455RR6+umn6fzzz6dcLkef+cxniOi1HNVll11GP/3pT2n9+vWUyWTopJNOom9/+9tTzumxxx6jq666ipYsWULpdJoWL15Mf/7nf06lUonFXXfddZTP56m3t5euuOIKyufz1NnZSbfccgsFQcBiwzCkL3/5y3TyySdTJpOh7u5uuuGGG2h4eHh6Nw7MaXp7e+nDH/4wLViwgNLpNC1btoxuuukmqlarNDQ0RLfccgudeuqplM/nqampiS699FJ69tlnJ49/9NFH6e1vfzsREV1//fXkOA45jkP33HPPLF0RmMsMDAzQ1VdfTU1NTdTe3k5/9md/RuVyefLfpWbgnnvuIcdx6Be/+AXdfPPN1NXVRYsWLZr8969//eu0YsUKymazdNZZZ9Fjjz02k5fzlmLOpAmeeuop+vWvf03XXHMNLVq0iPbt20df/epX6YILLqAdO3ZQLpd73eMHBwfp0ksvpWuuuYY+9KEPUXd39+S/7dq1iz74wQ/SjTfeSJs3b6YtW7bQVVddRQ899BBddNFFxxxz69atNDExQTfddBO1t7fTk08+SXfddRcdPHiQtm7dymKDIKBNmzbRO97xDvr7v/97evjhh+mOO+6gFStW0E033TQZd8MNN9A999xD119/PX3iE5+gvXv30le+8hXatm0b/epXv5py1wziw6FDh+iss86iQqFAH/vYx2jt2rXU29tLDz74IE1MTNCePXvou9/9Ll111VW0bNky6uvro6997Wu0ceNG2rFjBy1YsIDWrVtHX/jCF+jWW2+lj33sY3TeeecREdHZZ589y1cH5iJXX3019fT00O23306/+c1v6L/+1/9Kw8PDdN99973ucTfffDN1dnbSrbfeOvmXgW984xt0ww030Nlnn02f/OQnac+ePXT55ZdTW1sbLV68eCYu561FNEeYmJhQfY8//nhERNF999032ffII49ERBQ98sgjk30bN26MiCi6++671RhLly6NiCj61re+Ndk3MjISzZ8/PzrjjDNed1xrTrfffnvkOE60f//+yb7NmzdHRBR94QtfYLFnnHFGtGHDhsn2Y489FhFRdP/997O4hx56yOwH8ebaa6+NXNeNnnrqKfVvYRhG5XI5CoKA9e/duzdKp9NsLT711FMREUVbtmw53lMGMeVzn/tcRETR5ZdfzvpvvvnmiIiiZ599Noqi177Hmzdvnvz3LVu2REQUnXvuuZHv+5P91Wo16urqitavXx9VKpXJ/q9//esREUUbN248rtfzVmTOpAmy2ezk/6/VajQ4OEgrV66klpYWeuaZZ6Y8Pp1O0/XXX2/+24IFC+jKK6+cbDc1NdG1115L27ZtoyNHjtQ1p2KxSAMDA3T22WdTFEW0bds2FX/jjTey9nnnnUd79uyZbG/dupWam5vpoosuooGBgcn/bdiwgfL5PD3yyCNTXieIB2EY0ne/+116//vfT2eeeab6d8dxKJ1OT2pngiCgwcFByufztGbNmrreGQDebP7kT/6EtT/+8Y8TEdGPf/zj1z3uox/9KCUSicn2b3/7W+rv76cbb7yRicWvu+46am5ufhNnPHeYM2mCUqlEt99+O23ZsoV6e3spiqLJfxsZGZny+IULFx7zvzBYuXIlOY7D+lavXk1ERPv27aN58+aZxx04cIBuvfVW+v73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lcUGQUCbNm2id7zjHfT3f//39PDDD9Mdd9xBK1asoJtuumky7oYbbqB77rmHrr/+evrEJz5Be/fupa985Su0bds2+tWvfkWe503zDoK5AL67s0wEFFdccUWUyWSi/fv3T/bt2LEjSiQS0f+5Zdu3b4+IKPrIRz7Cjr3lllsiIor+7d/+LYqiKDpy5EiUTCajK664gsXddtttERFFmzdvPr4XA054JiYmVN/jjz8eEVF03333TfY98sgjERFFjzzyyGTfxo0bIyKK7r77bjXG0qVLIyKKvvWtb032jYyMRPPnz4/OOOOM1x3XmtPtt98eOY7D3ovNmzdHRBR94QtfYLFnnHFGtGHDhsn2Y489FhFRdP/997O4hx56yOwH8QPf3dkFaQJBEAT0k5/8hK644gpasmTJZP+6deto06ZNk+0f//jHRET0n/7Tf2LHf+pTnyIioh/96EdERPTzn/+cfN+nm2++mcV9/OMfPy7zB289stns5P+v1Wo0ODhIK1eupJaWFnrmmWemPD6dTtP1119v/tuCBQvoyiuvnGw3NTXRtddeS9u2baMjR47UNadisUgDAwN09tlnUxRFtG3bNhV/4403svZ5551He/bsmWxv3bqVmpub6aKLLqKBgYHJ/23YsIHy+Tw98sgjU14nmLvguzv7YDMgOHr0KJVKJVq1apX6tzVr1kz+//3795PrurRy5UoWM2/ePGppaaH9+/dPxhGRimtra6PW1tY3e/rgLUipVKJbb72VFi9eTOl0mjo6Oqizs5MKhQKNjIxMefzChQuP+V8YrFy5Uv032qtXryYiUpqE3+fAgQN03XXXUVtb26QOYOPGjUREak6ZTIY6OztZX2trK9MC7Nq1i0ZGRqirq4s6OzvZ/8bHx6m/v3/K6wRzF3x3Zx9oBt4gMMMAb5SPf/zjtGXLFvrkJz9J73rXu6i5uZkcx6FrrrmGwjCc8vjf/y3+zSAIArroootoaGiI/uqv/orWrl1LDQ0N1NvbS9ddd52aUyKRmHLMMAypq6uL7r//fvPf5WYCgNcD3903H2wGBJ2dnZTNZmnXrl3q315++eXJ/7906VIKw5B27dpF69atm+zv6+ujQqFAS5cunYwjItq9ezctW7ZsMm5wcBAqakBERA8++CBt3ryZ7rjjjsm+crlMhULhDY+9e/duiqKIfTx37txJRK/91wYW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",
      "text/plain": [
       "<Figure size 640x480 with 6 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "data_iter = next(dataset_train.create_dict_iterator())\n",
    "\n",
    "images = data_iter[\"image\"].asnumpy()\n",
    "labels = data_iter[\"label\"].asnumpy()\n",
    "print(f\"Image shape: {images.shape}, Label shape: {labels.shape}\")\n",
    "\n",
    "# 训练数据集中，前六张图片所对应的标签\n",
    "print(f\"Labels: {labels[:6]}\")\n",
    "\n",
    "classes = []\n",
    "\n",
    "with open(data_dir + \"/batches.meta.txt\", \"r\") as f:\n",
    "    for line in f:\n",
    "        line = line.rstrip()\n",
    "        if line:\n",
    "            classes.append(line)\n",
    "\n",
    "# 训练数据集的前六张图片\n",
    "plt.figure()\n",
    "for i in range(6):\n",
    "    plt.subplot(2, 3, i + 1)\n",
    "    image_trans = np.transpose(images[i], (1, 2, 0))\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465])\n",
    "    std = np.array([0.2023, 0.1994, 0.2010])\n",
    "    image_trans = std * image_trans + mean\n",
    "    image_trans = np.clip(image_trans, 0, 1)\n",
    "    plt.title(f\"{classes[labels[i]]}\")\n",
    "    plt.imshow(image_trans)\n",
    "    plt.axis(\"off\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "5bc7e066-ce5d-4a24-8e3e-a32991b15510",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from typing import Type, Union, List, Optional\n",
    "import mindspore.nn as nn\n",
    "from mindspore.common.initializer import Normal\n",
    "\n",
    "# 初始化卷积层与BatchNorm的参数\n",
    "weight_init = Normal(mean=0, sigma=0.02)\n",
    "gamma_init = Normal(mean=1, sigma=0.02)\n",
    "\n",
    "class ResidualBlockBase(nn.Cell):\n",
    "    expansion: int = 1  # 最后一个卷积核数量与第一个卷积核数量相等\n",
    "\n",
    "    def __init__(self, in_channel: int, out_channel: int,\n",
    "                 stride: int = 1, norm: Optional[nn.Cell] = None,\n",
    "                 down_sample: Optional[nn.Cell] = None) -> None:\n",
    "        super(ResidualBlockBase, self).__init__()\n",
    "        if not norm:\n",
    "            self.norm = nn.BatchNorm2d(out_channel)\n",
    "        else:\n",
    "            self.norm = norm\n",
    "\n",
    "        self.conv1 = nn.Conv2d(in_channel, out_channel,\n",
    "                               kernel_size=3, stride=stride,\n",
    "                               weight_init=weight_init)\n",
    "        self.conv2 = nn.Conv2d(in_channel, out_channel,\n",
    "                               kernel_size=3, weight_init=weight_init)\n",
    "        self.relu = nn.ReLU()\n",
    "        self.down_sample = down_sample\n",
    "\n",
    "    def construct(self, x):\n",
    "        \"\"\"ResidualBlockBase construct.\"\"\"\n",
    "        identity = x  # shortcuts分支\n",
    "\n",
    "        out = self.conv1(x)  # 主分支第一层：3*3卷积层\n",
    "        out = self.norm(out)\n",
    "        out = self.relu(out)\n",
    "        out = self.conv2(out)  # 主分支第二层：3*3卷积层\n",
    "        out = self.norm(out)\n",
    "\n",
    "        if self.down_sample is not None:\n",
    "            identity = self.down_sample(x)\n",
    "        out += identity  # 输出为主分支与shortcuts之和\n",
    "        out = self.relu(out)\n",
    "\n",
    "        return out"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "5953a980-1b18-4a3b-aed2-1e6ec0405300",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class ResidualBlock(nn.Cell):\n",
    "    expansion = 4  # 最后一个卷积核的数量是第一个卷积核数量的4倍\n",
    "\n",
    "    def __init__(self, in_channel: int, out_channel: int,\n",
    "                 stride: int = 1, down_sample: Optional[nn.Cell] = None) -> None:\n",
    "        super(ResidualBlock, self).__init__()\n",
    "\n",
    "        self.conv1 = nn.Conv2d(in_channel, out_channel,\n",
    "                               kernel_size=1, weight_init=weight_init)\n",
    "        self.norm1 = nn.BatchNorm2d(out_channel)\n",
    "        self.conv2 = nn.Conv2d(out_channel, out_channel,\n",
    "                               kernel_size=3, stride=stride,\n",
    "                               weight_init=weight_init)\n",
    "        self.norm2 = nn.BatchNorm2d(out_channel)\n",
    "        self.conv3 = nn.Conv2d(out_channel, out_channel * self.expansion,\n",
    "                               kernel_size=1, weight_init=weight_init)\n",
    "        self.norm3 = nn.BatchNorm2d(out_channel * self.expansion)\n",
    "\n",
    "        self.relu = nn.ReLU()\n",
    "        self.down_sample = down_sample\n",
    "\n",
    "    def construct(self, x):\n",
    "\n",
    "        identity = x  # shortcuts分支\n",
    "\n",
    "        out = self.conv1(x)  # 主分支第一层：1*1卷积层\n",
    "        out = self.norm1(out)\n",
    "        out = self.relu(out)\n",
    "        out = self.conv2(out)  # 主分支第二层：3*3卷积层\n",
    "        out = self.norm2(out)\n",
    "        out = self.relu(out)\n",
    "        out = self.conv3(out)  # 主分支第三层：1*1卷积层\n",
    "        out = self.norm3(out)\n",
    "\n",
    "        if self.down_sample is not None:\n",
    "            identity = self.down_sample(x)\n",
    "\n",
    "        out += identity  # 输出为主分支与shortcuts之和\n",
    "        out = self.relu(out)\n",
    "\n",
    "        return out"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "a53c60de-974f-4bc0-88b6-9268b32424d9",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def make_layer(last_out_channel, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n",
    "               channel: int, block_nums: int, stride: int = 1):\n",
    "    down_sample = None  # shortcuts分支\n",
    "\n",
    "    if stride != 1 or last_out_channel != channel * block.expansion:\n",
    "\n",
    "        down_sample = nn.SequentialCell([\n",
    "            nn.Conv2d(last_out_channel, channel * block.expansion,\n",
    "                      kernel_size=1, stride=stride, weight_init=weight_init),\n",
    "            nn.BatchNorm2d(channel * block.expansion, gamma_init=gamma_init)\n",
    "        ])\n",
    "\n",
    "    layers = []\n",
    "    layers.append(block(last_out_channel, channel, stride=stride, down_sample=down_sample))\n",
    "\n",
    "    in_channel = channel * block.expansion\n",
    "    # 堆叠残差网络\n",
    "    for _ in range(1, block_nums):\n",
    "\n",
    "        layers.append(block(in_channel, channel))\n",
    "\n",
    "    return nn.SequentialCell(layers)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "178e32bb-a88e-4c4a-9ee8-0d249f38c965",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from mindspore import load_checkpoint, load_param_into_net\n",
    "\n",
    "\n",
    "class ResNet(nn.Cell):\n",
    "    def __init__(self, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n",
    "                 layer_nums: List[int], num_classes: int, input_channel: int) -> None:\n",
    "        super(ResNet, self).__init__()\n",
    "\n",
    "        self.relu = nn.ReLU()\n",
    "        # 第一个卷积层，输入channel为3（彩色图像），输出channel为64\n",
    "        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, weight_init=weight_init)\n",
    "        self.norm = nn.BatchNorm2d(64)\n",
    "        # 最大池化层，缩小图片的尺寸\n",
    "        self.max_pool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode='same')\n",
    "        # 各个残差网络结构块定义\n",
    "        self.layer1 = make_layer(64, block, 64, layer_nums[0])\n",
    "        self.layer2 = make_layer(64 * block.expansion, block, 128, layer_nums[1], stride=2)\n",
    "        self.layer3 = make_layer(128 * block.expansion, block, 256, layer_nums[2], stride=2)\n",
    "        self.layer4 = make_layer(256 * block.expansion, block, 512, layer_nums[3], stride=2)\n",
    "        # 平均池化层\n",
    "        self.avg_pool = nn.AvgPool2d()\n",
    "        # flattern层\n",
    "        self.flatten = nn.Flatten()\n",
    "        # 全连接层\n",
    "        self.fc = nn.Dense(in_channels=input_channel, out_channels=num_classes)\n",
    "\n",
    "    def construct(self, x):\n",
    "\n",
    "        x = self.conv1(x)\n",
    "        x = self.norm(x)\n",
    "        x = self.relu(x)\n",
    "        x = self.max_pool(x)\n",
    "\n",
    "        x = self.layer1(x)\n",
    "        x = self.layer2(x)\n",
    "        x = self.layer3(x)\n",
    "        x = self.layer4(x)\n",
    "\n",
    "        x = self.avg_pool(x)\n",
    "        x = self.flatten(x)\n",
    "        x = self.fc(x)\n",
    "\n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "3cd6a7e5-2e8d-48bb-a8ed-c92b50c1fb95",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def _resnet(model_url: str, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n",
    "            layers: List[int], num_classes: int, pretrained: bool, pretrained_ckpt: str,\n",
    "            input_channel: int):\n",
    "    model = ResNet(block, layers, num_classes, input_channel)\n",
    "\n",
    "    if pretrained:\n",
    "        # 加载预训练模型\n",
    "        download(url=model_url, path=pretrained_ckpt, replace=True)\n",
    "        param_dict = load_checkpoint(pretrained_ckpt)\n",
    "        load_param_into_net(model, param_dict)\n",
    "\n",
    "    return model\n",
    "\n",
    "\n",
    "def resnet50(num_classes: int = 1000, pretrained: bool = False):\n",
    "    \"\"\"ResNet50模型\"\"\"\n",
    "    resnet50_url = \"https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt\"\n",
    "    resnet50_ckpt = \"./LoadPretrainedModel/resnet50_224_new.ckpt\"\n",
    "    return _resnet(resnet50_url, ResidualBlock, [3, 4, 6, 3], num_classes,\n",
    "                   pretrained, resnet50_ckpt, 2048)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "2abdcdf8-dfab-44ad-8ec8-3d58d6426fbf",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading data from https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt (97.7 MB)\n",
      "\n",
      "file_sizes: 100%|█████████████████████████████| 102M/102M [00:00<00:00, 234MB/s]\n",
      "Successfully downloaded file to ./LoadPretrainedModel/resnet50_224_new.ckpt\n"
     ]
    }
   ],
   "source": [
    "# 定义ResNet50网络\n",
    "network = resnet50(pretrained=True)\n",
    "\n",
    "# 全连接层输入层的大小\n",
    "in_channel = network.fc.in_channels\n",
    "fc = nn.Dense(in_channels=in_channel, out_channels=10)\n",
    "# 重置全连接层\n",
    "network.fc = fc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "3a552eb4-9e5c-436b-be10-54dcd7d1fd87",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 设置学习率\n",
    "num_epochs = 5\n",
    "lr = nn.cosine_decay_lr(min_lr=0.00001, max_lr=0.001, total_step=step_size_train * num_epochs,\n",
    "                        step_per_epoch=step_size_train, decay_epoch=num_epochs)\n",
    "# 定义优化器和损失函数\n",
    "opt = nn.Momentum(params=network.trainable_params(), learning_rate=lr, momentum=0.9)\n",
    "loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')\n",
    "\n",
    "\n",
    "def forward_fn(inputs, targets):\n",
    "    logits = network(inputs)\n",
    "    loss = loss_fn(logits, targets)\n",
    "    return loss\n",
    "\n",
    "\n",
    "grad_fn = ms.value_and_grad(forward_fn, None, opt.parameters)\n",
    "\n",
    "\n",
    "def train_step(inputs, targets):\n",
    "    loss, grads = grad_fn(inputs, targets)\n",
    "    opt(grads)\n",
    "    return loss"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "a4aadb85-91bc-42f6-ac8e-3c6933a90b27",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "# 创建迭代器\n",
    "data_loader_train = dataset_train.create_tuple_iterator(num_epochs=num_epochs)\n",
    "data_loader_val = dataset_val.create_tuple_iterator(num_epochs=num_epochs)\n",
    "\n",
    "# 最佳模型存储路径\n",
    "best_acc = 0\n",
    "best_ckpt_dir = \"./BestCheckpoint\"\n",
    "best_ckpt_path = \"./BestCheckpoint/resnet50-best.ckpt\"\n",
    "\n",
    "if not os.path.exists(best_ckpt_dir):\n",
    "    os.mkdir(best_ckpt_dir)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "0fa8f81c-e29d-45f7-a2a1-83599cbfe168",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import mindspore.ops as ops\n",
    "\n",
    "\n",
    "def train(data_loader, epoch):\n",
    "    \"\"\"模型训练\"\"\"\n",
    "    losses = []\n",
    "    network.set_train(True)\n",
    "\n",
    "    for i, (images, labels) in enumerate(data_loader):\n",
    "        loss = train_step(images, labels)\n",
    "        if i % 100 == 0 or i == step_size_train - 1:\n",
    "            print('Epoch: [%3d/%3d], Steps: [%3d/%3d], Train Loss: [%5.3f]' %\n",
    "                  (epoch + 1, num_epochs, i + 1, step_size_train, loss))\n",
    "        losses.append(loss)\n",
    "\n",
    "    return sum(losses) / len(losses)\n",
    "\n",
    "\n",
    "def evaluate(data_loader):\n",
    "    \"\"\"模型验证\"\"\"\n",
    "    network.set_train(False)\n",
    "\n",
    "    correct_num = 0.0  # 预测正确个数\n",
    "    total_num = 0.0  # 预测总数\n",
    "\n",
    "    for images, labels in data_loader:\n",
    "        logits = network(images)\n",
    "        pred = logits.argmax(axis=1)  # 预测结果\n",
    "        correct = ops.equal(pred, labels).reshape((-1, ))\n",
    "        correct_num += correct.sum().asnumpy()\n",
    "        total_num += correct.shape[0]\n",
    "\n",
    "    acc = correct_num / total_num  # 准确率\n",
    "\n",
    "    return acc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "339897a8-caf6-4d60-93ab-471eb88719dd",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Start Training Loop ...\n",
      "Epoch: [  1/  5], Steps: [  1/196], Train Loss: [2.458]\n",
      "Epoch: [  1/  5], Steps: [101/196], Train Loss: [1.522]\n"
     ]
    }
   ],
   "source": [
    "# 开始循环训练\n",
    "print(\"Start Training Loop ...\")\n",
    "\n",
    "for epoch in range(num_epochs):\n",
    "    curr_loss = train(data_loader_train, epoch)\n",
    "    curr_acc = evaluate(data_loader_val)\n",
    "\n",
    "    print(\"-\" * 50)\n",
    "    print(\"Epoch: [%3d/%3d], Average Train Loss: [%5.3f], Accuracy: [%5.3f]\" % (\n",
    "        epoch+1, num_epochs, curr_loss, curr_acc\n",
    "    ))\n",
    "    print(\"-\" * 50)\n",
    "\n",
    "    # 保存当前预测准确率最高的模型\n",
    "    if curr_acc > best_acc:\n",
    "        best_acc = curr_acc\n",
    "        ms.save_checkpoint(network, best_ckpt_path)\n",
    "\n",
    "print(\"=\" * 80)\n",
    "print(f\"End of validation the best Accuracy is: {best_acc: 5.3f}, \"\n",
    "      f\"save the best ckpt file in {best_ckpt_path}\", flush=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7a4a00c0-4811-447e-9b37-cd3a03855b52",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "\n",
    "def visualize_model(best_ckpt_path, dataset_val):\n",
    "    num_class = 10\n",
    "    net = resnet50(num_class)\n",
    "    # 加载模型参数\n",
    "    param_dict = ms.load_checkpoint(best_ckpt_path)\n",
    "    ms.load_param_into_net(net, param_dict)\n",
    "    # 加载验证集的数据进行验证\n",
    "    data = next(dataset_val.create_dict_iterator())\n",
    "    images = data[\"image\"]\n",
    "    labels = data[\"label\"]\n",
    "    # 预测图像类别\n",
    "    output = net(data['image'])\n",
    "    pred = np.argmax(output.asnumpy(), axis=1)\n",
    "\n",
    "    # 图像分类\n",
    "    classes = []\n",
    "\n",
    "    with open(data_dir + \"/batches.meta.txt\", \"r\") as f:\n",
    "        for line in f:\n",
    "            line = line.rstrip()\n",
    "            if line:\n",
    "                classes.append(line)\n",
    "\n",
    "    # 显示图像及图像的预测值\n",
    "    plt.figure()\n",
    "    for i in range(6):\n",
    "        plt.subplot(2, 3, i + 1)\n",
    "        # 若预测正确，显示为蓝色；若预测错误，显示为红色\n",
    "        color = 'blue' if pred[i] == labels.asnumpy()[i] else 'red'\n",
    "        plt.title('predict:{}'.format(classes[pred[i]]), color=color)\n",
    "        picture_show = np.transpose(images.asnumpy()[i], (1, 2, 0))\n",
    "        mean = np.array([0.4914, 0.4822, 0.4465])\n",
    "        std = np.array([0.2023, 0.1994, 0.2010])\n",
    "        picture_show = std * picture_show + mean\n",
    "        picture_show = np.clip(picture_show, 0, 1)\n",
    "        plt.imshow(picture_show)\n",
    "        plt.axis('off')\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "# 使用测试数据集进行验证\n",
    "visualize_model(best_ckpt_path=best_ckpt_path, dataset_val=dataset_val)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "018bf361-1d87-45f8-9fe1-9f63e910ea36",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "02fce18b-a7a2-4f20-bd6c-4b0c88913fea",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.21"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
